// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <iostream>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include <memory>

#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/ftx/symbology/currency.h"

namespace coin2::exchange::ftx_futures::symbology {

using FtxCurrency = coin2::exchange::ftx::symbology::FtxCurrency;

class FtxFuturesProduct
    : public base::symbology::FuturesProduct<FtxCurrency, FtxFuturesProduct>,
      public base::symbology::ProductFromStrMixin<FtxFuturesProduct> {
 public:
  using FuturesProduct = base::symbology::FuturesProduct<FtxCurrency, FtxFuturesProduct>;
  using ContractType = base::symbology::ContractType;

 public:
  FtxFuturesProduct(
      const FtxCurrency& base,
      const FtxCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Ftx,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<FtxFuturesProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    std::unique_ptr<FtxCurrency> base;
    auto quote = FtxCurrency::FromNormStrNoThrow("USD", resolve_timestamp);
    std::string expiry_str;
    std::vector<std::string> splitted;
    splitted = Split(native_product, "-");
    if (splitted.size() == 2) {
      base = FtxCurrency::FromNativeStrNoThrow(splitted[0], resolve_timestamp);
      expiry_str = splitted[1];
      if (expiry_str == "PERP") {
        expiry_str = "PERPETUAL";
      }
    } else {
      return {};
    }
    CHECK(base) << native_product;

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = CalculateParams(
        *base,
        *quote,
        expiry_str,
        resolve_timestamp,
        &native_product_tmp,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp);
    if (success) {
      DCHECK_EQ(native_product_tmp, native_product);
      return std::make_unique<FtxFuturesProduct>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const FtxCurrency& base,
      const FtxCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::ExchangeType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::ToDateStr;

    static const int64_t expiry_time_of_day_nanosec = 3 * 3600 * 1e9;
    std::stringstream ss;

    ss.str(std::string());
    if (std::all_of(std::begin(expiry_str), std::end(expiry_str), ::isdigit)) {
      bool found = false;
      for (auto type : {ContractType::QUARTER, ContractType::NEXT_QUARTER, ContractType::NNEXT_QUARTER}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Ftx, type, resolve_timestamp, expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp) ||
            expiry_str == ToDateStr(timestamp).substr(4)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;
          ss << base.native_currency() << "-" << expiry_str.substr(expiry_str.size() - 4);
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
          ss << base.native_currency() << "-" << "PERP";
        } else {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Ftx, *contract_type, resolve_timestamp, expiry_time_of_day_nanosec);
          ss << base.native_currency() << "-"
             << ToDateStr(*expiry_timestamp).substr(4);
        }
      } else {
        return false;
      }
    }
    *native_product = ss.str();
    *native_subscription_symbol = *native_product;
    return true;
  }
};

}  // namespace coin2::exchange::ftx_futures::symbology
